Milvus 索引介绍
为什么需要索引?——从暴力搜索到近似搜索
生活中的类比:理解索引的本质
想象一下,你来到一个拥有百万册书籍的超大型图书馆,想要找到与《三体》最相似的10本书。如果没有目录索引系统,你需要怎么做?唯一的办法就是从第一个书架开始,逐本翻阅每一本书,比较它们与《三体》的相似度。这就是所谓的"全表扫描"或"暴力搜索"。
这种方法的致命缺陷在于它的线性增长特性。当图书馆只有1万册书时,你可能只需要几个小时就能完成搜索。但当藏书量增加到100万册时,搜索时间就会相应增加100倍。在计算机领域,我们称之为O(n)时间复杂度——数据量增长多少倍,处理时间就增长多少倍。
在实际的向量数据库应用中,这种暴力搜索方式会带来严重的性能问题。每个向量比较都需要进行数十次甚至数百次数学运算,当数据量达到百万级别时,总计算量将变得极其庞大,响应时间可能长达数秒甚至分钟级,完全无法满足实时查询的需求。 既然精确搜索的成本如此高昂,人们开始思考:我们是否真的需要百分之百精确的结果?在许多实际应用场景中,答案是否定的。
以视频网站推荐系统为例,当我们为用户推荐可能喜欢的视频时,并不需要找到"绝对最相似"的十个视频,而是需要找到"足够相似"且多样化的推荐内容。即使偶尔有一两个推荐结果不是最优解,用户通常也不会察觉,但推荐速度的提升却是明显可感知的。
这种思路催生了近似最近邻搜索技术。其核心思想是通过某种预处理方式,将数据组织成更容易搜索的结构。继续用图书馆的比喻:我们不再需要逐本翻阅所有书籍,而是先查看分类标签,只检索"科幻小说"区域内的书籍,或者通过作者索引直接找到刘慈欣的其他作品。
这种方法的巧妙之处在于它接受了一定程度的不完美,却换来了效率的质的飞跃。从需要检查百万本书籍,到只需要检查几千本,搜索速度可能提升数百倍,而精度损失可能只有百分之几。
向量索引的核心价值
向量索引解决了两个关键问题:
- 海量数据的快速检索:当你有百万、千万甚至亿级向量时,暴力搜索(逐一比较)会变得极其缓慢
- 近似的"足够好"结果:在大多数应用场景中,我们不需要100%精确的结果,95%的准确性已经足够,但速度提升是巨大的
这就是ANNS(近似近邻检索)的核心理念:通过在可接受范围内牺牲一定精确度,换取检索效率的显著提升。
索引的代价与收益
索引技术的本质是一种典型的"空间换时间"策略。就像图书馆需要额外空间来存放目录卡片和索引系统一样,向量索引也需要占用额外的存储空间。这种空间开销有时甚至可能超过原始数据本身的大小。
除了存储开销,索引还需要预处理时间。构建索引就像图书馆员需要花费时间整理图书分类、编写目录卡片一样,需要前期投入时间进行数据处理和组织。这个过程可能很耗时,特别是对于大规模数据集。
更重要的是精度与速度的权衡。不同的索引算法在这两者之间提供了不同的平衡点。有些索引追求极致的速度,愿意牺牲较多的精度;有些则偏向保守,以较小的速度提升换取更高的精度保证。
这种权衡没有绝对的最优解,完全取决于具体应用场景的需求。在医疗影像分析中,可能宁愿等待更长时间也要保证结果的绝对准确;而在实时商品推荐中,快速的近似结果可能比缓慢的精确结果更有价值。
理解这种权衡关系,是选择和优化索引方案的关键第一步。后续章节中介绍的各种索引类型,本质上都是在精度、速度、内存占用这三个维度上提供不同的配置选项。 总结一下:索引并非是万能,使用它需要付出以下代价
- 存储空间:索引需要额外的磁盘空间
- 内存占用:索引加载到内存中会消耗RAM
- 构建时间:创建索引需要预处理时间
- 精度损失:近似搜索可能错过一些真正的最相似结果
但相比收益(查询速度提升数十倍甚至数百倍),这些代价通常是值得的。
在了解了索引的代价与收益后,我们深入探究 ANNS 技术,它正是众多索引背后的核心技术支撑,下面来看看其核心技术理念与价值。
ANNS技术核心与价值
ANNS(近似近邻检索)的核心技术理念在于不局限于返回最精确的topK结果,而是通过在可接受范围内牺牲一定精确度,换取检索效率的显著提升。其核心价值体现在能够有效加快大型数据集上相似性搜索的速度,从而提升此类查询的实用性 。
作为实现ANNS的关键组件,索引是建立在数据之上的附加结构,其内部结构取决于所采用的具体ANNS算法 。在向量数据库Milvus中,索引作为字段级结构存在,主要用于提升向量搜索和标量过滤性能 。然而,索引的构建和使用并非无代价:它会产生额外的预处理时间、存储空间及RAM开销,同时可能导致召回率的轻微降低(尽管这种影响通常可忽略,但仍需关注) 。因此,在实际应用中需权衡索引的预处理成本与搜索收益。
索引分类
ANNS向量索引的分类可从多个维度构建,核心包括实现方法与向量嵌入类型两大逻辑维度,不同维度间存在交叉关联,共同构成索引技术的全局认知框架。
从实现方法角度,ANNS向量索引可分为四大类:基于树结构、基于图结构、基于哈希方法和基于量化技术。基于树结构的索引通过空间划分构建层级树(如KD树),实现向量的快速检索;基于图结构的索引通过构建近邻图(如HNSW),利用图遍历高效定位相似向量;基于哈希方法的索引将高维向量映射到低维哈希码,通过哈希表实现快速查找;基于量化技术的索引则通过向量编码压缩(如乘积量化PQ、标量量化SQ),在降低存储和计算成本的同时保持检索性能。
实现方法分类 | 核心技术原理 | 典型代表算法 |
---|---|---|
基于树结构 | 空间划分构建层级树 | KD树 |
基于图结构 | 构建近邻图实现高效遍历 | HNSW |
基于哈希方法 | 高维向量映射到低维哈希码 | 局部敏感哈希(LSH) |
基于量化技术 | 向量编码压缩降低存储成本 | 乘积量化(PQ)、标量量化(SQ) |
从向量嵌入类型角度,索引可按处理的数据类型划分为浮点嵌入索引、二进制嵌入索引、稀疏嵌入索引及GPU索引。浮点嵌入索引支持各类浮点精度向量(如FLOAT32、FLOAT16),典型代表包括FLAT、IVF系列(IVF_FLAT、IVF_SQ8、IVF_PQ)、HNSW、SCANN、DiskANN等;二进制嵌入索引针对二值化向量,如BIN_FLAT、BIN_IVF_FLAT;稀疏嵌入索引适用于非零元素占比低的稀疏向量,如SPARSE_INVERTED_INDEX、SPARSE_WAND;GPU索引则通过图形处理器加速计算,如GPU_CAGRA、GPU_IVF_FLAT等。
嵌入类型 | 支持数据类型 | 典型索引示例 |
---|---|---|
浮点嵌入索引 | FLOAT32、FLOAT16等精度向量 | FLAT、IVF系列、HNSW、SCANN、DiskANN |
二进制嵌入索引 | 二值化向量 | BIN_FLAT、BIN_IVF_FLAT |
稀疏嵌入索引 | 非零元素占比低的稀疏向量 | SPARSE_INVERTED_INDEX、SPARSE_WAND |
GPU索引 | 各类向量(需GPU加速) | GPU_CAGRA、GPU_IVF_FLAT、GPU_IVF_PQ |
不同分类维度存在显著关联性。例如,基于量化技术的索引(如IVF_SQ8、IVF_PQ)主要属于浮点嵌入索引,通过量化压缩浮点向量以平衡性能与资源消耗;二进制嵌入索引的实现常结合量化或哈希方法,将浮点向量二值化后构建索引。以Milvus为例,其索引类型与目标字段数据类型严格映射:浮点向量字段支持IVF系列、HNSW等多种实现方法的索引;二进制向量字段对应BIN_FLAT等专用索引;稀疏浮点向量字段则适配稀疏反转索引,体现了向量嵌入类型与实现方法的协同选择逻辑。
归一化
这一部分是学习索引的基础,需了解,但如果您不考虑极致的优化,下面的内容看一看知道即可。下面我们将倒着向您介绍这一部分。
归一化是干嘛的?用专业术语来解释,归一化是把向量中的元素缩放到一个固定的范围,比如0到1。 公式: 对于向量 V = [v₁, v₂, ..., vₙ]
,其 L2 范数为 ||V||₂ = √(v₁² + v₂² + ... + vₙ²)
。归一化后的向量 V_norm 为: V_norm = V / ||V||₂ = [v₁/||V||₂, v₂/||V||₂, ..., vₙ/||V||₂]
我们举个例子: 小刘和小牧一起跑步,小刘跑了100米,小牧跑了200米。 小刘用了10秒,小牧用了20秒。谁更快?我们用什么来判断呢,距离?时间?按照距离判断的话,小牧跑了200米,那说明小牧更快,按照时间判断的话,小刘跑了100米,小牧跑了200米,小刘用了10秒,小牧用了20秒。小刘更快。看到这里,你会发现这样计算不公平也都不对。 此时,就需要用到归一化了,归一化就是一个“公平”的比较标准,我们计算他们的速度,你可以发现小刘和小牧的速度都一样,那么现在,我们比较速度这个归一化后的值,可以发现,两个人一样快,那么这个速度就是归一化后的结果,它消除了跑的距离不同这个因素的影响,只关注快慢本身。
现在让我们回到向量的场景上,现在请你想象向量为一个箭头,这个箭头有方向和长度,**归一化就是让这个箭头的长度变为1,但方向不变。**是不是有点点不明白,下面将详细解释一下:
在很多时候,我们关心的知识两个箭头的方向是否相似,(两个箭头可以是两端文本或者是两个图片)方向相似,那么才能说明向量相似。但是,如果两个箭头都很长,即使方向有点不同,它们的内积(IP)也可能很大,欧氏距离(L2)也可能不小。如果两个箭头都很短,即使方向差别很大,内积可能很小,欧氏距离也可能不大。长度影响了我们对“方向相似度”的判断!。当所有向量长度都是1时,计算欧氏距离(L2)的大小顺序(谁小谁更近)和计算内积(IP)的大小顺序(谁大谁更相似)是完全一致的! 它们描述的是同一件事(方向相似度)的两种不同表达方式(距离 vs 相似度分数)。
现在,让我们回到公式上,
假设你有一个向量 V = [3, 4]
(想象一个从原点指向点(3,4)的箭头)。
Step 1: 计算这个向量的长度(L2范数) 长度 = ||V||₂ = √(3² + 4²) = √(9 + 16) = √25 = 5
Step 2: 把向量中的每一个数字都除以这个长度归一化后的向量 V_norm = [3/5, 4/5] = [0.6, 0.8]
验证新向量的长度: ||V_norm||₂ = √(0.6² + 0.8²) = √(0.36 + 0.64) = √1 = 1
。成功!长度变成了1。
至此,归一化的介绍结束了,Milvus官方网站中有下面的一段内容:
为什么欧氏距离(L2)和内积(IP)返回的结果不同? 对于归一化向量,欧氏距离(L2)在数学上等同于内积(IP)。如果这些相似性度量返回不同的结果,请检查您的向量是否归一化.
当您看到这里时,是不是就可以理解了,如果你的 L2 距离结果 和 IP 内积结果 排序不同,首要怀疑对象就是:你的向量没有归一化!再去看看代码,或者,直接扔给模型,告诉他帮我改进我的代码!
应用场景以及性能差距
欧氏距离和内积是两种计算方法,选择L2还是IP,主要取决于你的具体任务和对相似性的定义:
对于欧氏距离L2来说,其核心思想是衡量两个点之间在空间中的绝对距离。
- 在之后我们学习的聚类算法介绍中,我们可以看到K-means聚类算法就用到了l2距离,目标是计算最小化簇内点与质心之间的距离。
- 如果您在计算向量的时候,更加关心特征空间中位置的距离,那么L2距离是一个更好的选择。例如,在图像处理中,L2距离可以用来衡量两个图像之间的相似度。
- 当向量本身不包含重要信息,或者以及被归一化了,L2和IP的排序等价,L2是距离,会更加直观。值越小表示越相似。
对于内积IP来说,其核心思想是衡量两个点之间的方向关系。
- 在自然语言NLP中,词向量和句子向量之间的相似度可以使用余弦相似度来衡量,余弦相似度就是内积的一种形式。
- 如果您在计算向量的时候,更加关心特征空间中方向的一致性,那么IP内积是一个更好的选择。例如,在推荐系统中,IP内积可以用来衡量用户和物品之间的兴趣相似度。
- 值越大表示越相关,值可正可负,负值表示负相关/相反方向。
在计算复杂度上,L2和IP的计算复杂度都是O(d),其中d是向量的维度。因此,在维度较高的情况下,两者的计算复杂度是相同的。 但是!我们来分析一下两者的计算步骤:
L2(A, B):
计算差值向量 C = A - B (需要 n 次减法)。
计算 C 中每个元素的平方 (需要 n 次乘法)。
求平方和 (需要 (n-1) 次加法)。
开平方根 (1 次相对昂贵的运算)。
IP(A, B): 需要 n 次乘法 + (n-1) 次加法。
你可以看到L2 比 IP 多做了 n 次减法和 1 次开平方根运算。对于单次计算,L2的开销要比IP要高,但这种差异在绝大多数场景下均是可忽略的。主要的时间消耗在于n 次乘法和 (n-1) 次加法,
如果需要物理距离或形状匹配感,或者向量已归一化且长度无意义,优先用 L2。
如果需要衡量相关性,或者向量长度本身编码了重要信息(如推荐系统中的物品热度),优先用 IP (或未归一化的余弦)。
欧氏距离L2
关于这一部分,我们说的是你在使用milvus的场景下! 当选择欧氏距离作为距离度量时,Milvus 只计算应用平方根之前的值1。
这意味着Milvus在计算欧氏距离时会跳过开平方根的步骤,只计算平方和的结果。
其中 a = (a₀, a₁, ..., aₙ₋₁) 和 b = (b₀, b₁, ..., bₙ₋₁) 是n维欧氏空间中的两个点1。
正常情况下,欧氏距离需要计算完平方和后再开平方根。但是Milvus为了优化性能,只计算平方根之前的值,也就是只计算 Σ(aᵢ - bᵢ)² 这部分,而不进行最后的开平方根操作1。
这样做的好处是:
减少计算量,提高性能 对于相似性比较来说,开不开平方根不影响排序结果,因为平方根是单调递增函数 所以虽然数值上不是真正的欧氏距离,但在向量相似性搜索中,这种优化不会影响搜索结果的准确性。
内积IP
两个向量在相同维度空间中的一种运算,结果是一个标量(单个数字)。值越大(正值),表示方向越接近;值为 0 表示正交(垂直);值为负表示方向相反。它也间接包含了向量长度信息(模长)。
核心索引类型技术原理与特性
浮点嵌入索引
精确检索索引:FLAT
FLAT索引代表了向量搜索中最直接了当的方法——暴力搜索。这种索引方式不对向量进行压缩,采用暴力搜索(即穷举搜索)策略,在每次查询时将目标向量与数据集中的所有向量进行逐一比较。这一特性使得FLAT能够保证100%的召回率,也是目前唯一能实现这一结果的索引类型。因此,FLAT索引在小规模数据集(通常为百万量级)或对检索精度有极高要求的场景中具有不可替代性,尤其适用于需要完美精确度的任务。
想象一下在一个巨大的图书馆里寻找特定书籍的场景。如果使用FLAT索引,就相当于不借助任何目录系统,从第一个书架开始,逐本检查每本书是否符合你的需求。这种方法虽然极其耗时,但却能保证你不会错过任何一本可能相关的书籍。
FLAT索引的核心价值在于它提供了绝对精确的结果。在某些对精度要求极高的场景中,这种保证是无可替代的。例如在医疗影像分析中,即使只有一个可疑病灶被漏检,都可能造成严重的临床后果。又如在法律文档检索中,任何相关判例的遗漏都可能影响案件的结果。
然而,这种精确性是以巨大的计算开销为代价的。当数据量增长时,FLAT索引的搜索时间呈线性增长,很快就会达到无法接受的程度。这就好比在一个拥有千万册藏书的图书馆中逐本翻阅——理论上可行,但实际上完全不现实。
FLAT索引最适合的是那些数据量不大但对精度要求极高的场景,不适合处理海量数据。一般来说,当向量数量在十万条以内时,FLAT索引还能提供相对可接受的性能。超过这个规模,就需要考虑其他更高效的索引方式了。在参数设置方面,FLAT索引较为简单,仅需指定距离计算方式(如L2欧氏距离或IP内积),且无需额外的训练过程 。此外,FLAT索引的检索结果常被用作其他近似索引的性能比较基准,为评估不同索引的召回率提供参考标准。
总结一下上面的描述,可以总结为:FLAT索引是最简单直观的索引类型,它的工作原理就像在图书馆里一本一本地翻书:
# 创建FLAT索引
index_params = {
"index_type": "FLAT",
"metric_type": "L2"
}
collection.create_index(field_name="embedding", index_params=index_params)
- 穷举比较:将查询向量与数据集中每一个向量进行逐一比较
- 精确计算:使用完整的向量数据计算距离,不做任何近似
- 100%召回:保证返回的结果是真正的最相似向量
特性 | 详细说明 |
---|---|
索引类型 | 精确检索索引,采用暴力搜索(穷举搜索)策略 |
核心原理 | 不对向量进行压缩,每次查询时将目标向量与数据集中所有向量逐一比较 |
召回率 | 100%,是目前唯一能实现这一结果的索引类型 |
适用场景 | 小规模数据集(通常为百万量级)或对检索精度有极高要求的场景 |
搜索速度 | 较慢(由于线性扫描本质) |
海量数据适用性 | 不适合处理海量数据 |
参数设置 | 仅需指定距离计算方式(如L2欧氏距离或IP内积),无需额外训练过程 |
主要用途 | 精确检索任务、作为其他近似索引的性能比较基准 |
那什么时候用这个FLAT索引呢?
首先,当我们的数据量很小的时候,比如几千或者几万条数据,并且对精度要求极高的情况下,比如你客户突然发给你了他们的产品的一堆文档,让你做一个关于这个产品的客服,当然,如果你真的有这个需求的话,简单的加上这个FLAT索引是一个非常合适的选择,但不是你唯一需要做的,你需要考虑的主要是模型的回复限制,以及前期用户的输入问题的拦截,为什么要这样考虑呢,主要有这些顾虑:首先如果客户文档是政务类型的文档数据,你对其进行向量化后,选择FLAT索引进行暴力搜索,尽管我们可以获取到最精确的搜索结果,但你不能保证模型对于用户问题的理解是否完全正确,除此之外,你也不能完全的保证模型的输出是否存在部分幻觉内容,关于这个部分,最近的一篇论文中写道:
为什么模型会出现幻觉 模型的幻觉就是在生成内容时产生与事实不符、缺乏依据或逻辑矛盾的输出。这可能是由于训练数据存在偏差、噪声,模型对复杂语义理解不充分,或者在推理过程中对不确定性的处理不当等原因导致的。例如在语言模型中,可能会编造出不存在的事件、人物或信息。 上面的这一行内容就是我让模型生成的,他说的对吗?在为什么模型会出现幻觉论文中,作者提到了模型出现幻觉的原因:模型之所以会产生幻觉,是因为**现有的训练和评估流程在奖励“猜测”,而不是“承认不确定性”。 具体来说: 1. 训练压力:模型的训练目标是预测最有可能的下一个词,这使得它们在面对不确定性时,倾向于生成一个看似合理但不一定正确的答案,而不是直接表示“我不知道”。 2. 评估偏差:绝大多数的评测基准都采用二元评分,即答案“非对即错”。在这种模式下,模型给出正确答案得1分,而回答“我不知道”或给出错误答案都得0分。这就像一个学生在参加考试,当不确定答案时,猜测一个选项至少有蒙对的可能,而交白卷则肯定没分。为了在这些评测中获得高分,模型被优化得更倾向于“冒险猜测”。 因此,即使像我们设想的那样,使用FLAT索引提供了最精确的上下文信息,语言模型在综合这些信息并生成最终答案时,仍然可能因为其“考生心态”而产生幻觉。它可能会过度解读信息,或者在信息不足以回答问题时,选择编造一个看似连贯的答案,而不是坦诚地表示信息不足。
综上所述,我们不能盲目的选择FLAT索引,选择FLAT索引是确保RAG系统中“检索”环节准确无误的有效手段,但这只是构建可信赖AI系统的一部分。我们还必须关注“生成”环节的模型行为,一个完整的解决方案需要结合精确的检索工具和经过专门优化、鼓励表达不确定性的语言模型,以及在系统层面设计好多轮对话管理和事实核验的机制(这个最重要哦)。
基于量化的索引:IVF系列
倒排索引如同图书馆的分类目录,它会将数据预先划分成多个类别,比如 “科技类”“文学类” 等,在执行搜索任务时,只需在相关类别内进行数据比对,从而减少搜索范围。
倒排索引主要由词汇表和倒排记录表(Posting List, PL)组成,词汇表存储着在所有文档集合中出现过的词汇,倒排记录表记录包含对于每一个词汇项对应的所有文档标识符(即文档的ID)以及词频、位置等信息。
假设有以下文档集合: 文档1: "Hello world" 文档2: "Hello DataWhale" 文档3: "Hello easy_vectorDB"
>构建倒排索引后,结果如下:
> Hello: [文档1, 文档2, 文档3]
> world: [文档1]
> DataWhale: [文档2]
> easy_vectorDB: [文档3]
IVF系列索引的核心机制是通过基于中心点的分区策略将向量聚类到多个桶中,在搜索过程中仅扫描与查询向量中心点相近的桶内向量,从而显著降低计算成本。 上图中你可以看到IVF系列索引对数据的聚类,通过不同的聚类算法将数据聚类到不同的桶中,每一聚类都有一个聚类中心。
每一个聚类中心对应的有一个向量表示,如上图所示。 当我们提供一个查询向量时,会先计算该向量与所有聚类中心之间的距离,然后选择距离最近的聚类中心,并仅搜索该聚类中心内的向量。通过这种方式,IVF系列索引能够显著减少搜索空间,提高搜索效率。但这种方法也存在缺点,在进行近似最近邻搜索时,可能会出现一种情况:通过算法找到的“最接近查询嵌入的候选嵌入”实际上并不是真正的最近邻。
这是因为IVF系列索引在聚类过程中,可能会将距离较远的向量分配到同一个聚类中,导致在搜索过程中,虽然找到了距离较近的聚类中心,但该聚类中心内的向量可能并不包含真正的最近邻。因此,IVF系列索引更适合用于近似最近邻搜索,而不是精确最近邻搜索。
具体来说,当使用聚类方法(例如IVF_FLAT中的k-means)对数据点进行分组时,每个聚类都有一个中心点(centroid)。在查询时,算法首先会确定哪个聚类的中心点与查询向量最为接近,然后在该聚类内部查找最近的向量作为候选嵌入。然而,如果实际的最近邻(即距离查询向量最近的数据点)位于另一个聚类中,而这个聚类的中心点与查询向量的距离相对更远,那么根据中心点选择的聚类将不会包含真实的最近邻。这就导致了找到的候选嵌入不是准确的最近嵌入。
为了解决这个问题,IVF_FLAT提供了两个超参数供我们调整:
nlist:指定整个数据集将被分成多少个子集(即使用k-means算法创建的聚类中心的数量)。增加这个值可以使得每个分区内的向量更加相似,但同时也会增加计算开销。通常设为数据量的平方根,如100万数据可设为1000。nlist越大,每个聚类内向量越少,搜索越快,但聚类质量可能下降
nprobe:搜索期间会检查的分区数量。增大这个值可以提高召回率(即找到真正最近邻的可能性),因为更多的候选分区被考虑进来,但这同样会增加搜索时间。通常为nlist的1%-10%,如nlist=1024时,nprobe可设为8-100。nprobe越大,召回率越高,但搜索时间越长
通过增加nprobe 值,可以在搜索中包含更多分区,这有助于确保不会错过与查询最接近的嵌入,即使它位于不同的分区中。不过,这样做的代价是增加搜索时间,因为需要评估更多候选项。
详细代码示例
# IVF_FLAT - 不压缩,保持原始精度
ivf_flat_params = {
"index_type": "IVF_FLAT",
"metric_type": "L2",
"params": {"nlist": 1024}
}
# IVF_SQ8 - 标量量化,4字节压缩为1字节
ivf_sq8_params = {
"index_type": "IVF_SQ8",
"metric_type": "L2",
"params": {"nlist": 1024}
}
# IVF_PQ - 乘积量化,超高压缩
ivf_pq_params = {
"index_type": "IVF_PQ",
"metric_type": "L2",
"params": {
"nlist": 1024,
"m": 8, # 将向量分成8个子向量
"nbits": 8 # 每个子向量用8位表示(256个码字)
}
}
IVF系列包含IVF_FLAT、IVF_SQ8和IVF_PQ三种主要类型,其技术演进逻辑围绕压缩率提升与精度平衡展开,逐步实现内存占用的降低。
索引类型 | 构建参数 | 搜索参数 | 压缩技术 | 内存占用 | 召回率 |
---|---|---|---|---|---|
IVF_FLAT | nlist(范围[1,65536],默认128) | nprobe(范围[1,nlist],默认8)、max_empty_result_buckets(范围[1,65535],默认2) | 无压缩 | 最高(与原始数据相近) | 最优 |
IVF_SQ8 | nlist(范围[1,65536],默认128) | nprobe(范围[1,nlist],默认8)、max_empty_result_buckets(范围[1,65535],默认2) | 标量量化(SQ),将FLOAT(4字节)转换为UINT8(1字节) | 中等(减少70-75%内存消耗) | 次之 |
IVF_PQ | nlist(范围[1,65536],默认128)、m(乘积量化因子数,需满足dim mod m=0)、nbits(范围[1,64],默认8) | nprobe(范围[1,nlist],默认8)、max_empty_result_buckets(范围[1,65535],默认2) | 乘积量化(PQ),将高维向量分解为低维子向量并量化 | 最低 | 最低 |
IVF_FLAT作为系列基础,是FLAT索引的改进版本,其核心特点是每个桶内存储原始向量,未进行压缩处理。构建时需指定nlist参数(聚类数量,取值范围[1,65536],默认128)以确定桶的数量;搜索阶段支持普通搜索(通过nprobe参数设置查询桶数,范围[1,nlist],默认8)和范围搜索(通过max_empty_result_buckets参数控制最大空白桶数,范围[1,65535],默认2)。由于保留原始向量,IVF_FLAT的索引文件大小与原始数据相近,内存占用最高,但召回率表现最优。
在IVF_FLAT基础上,IVF_SQ8通过引入标量量化(SQ)技术实现压缩,将每个4字节的FLOAT类型向量分量转换为1字节的UINT8类型,可减少70-75%的内存消耗(例如1B规模的SIFT数据集仅需140GB存储空间)。其构建参数(nlist)和搜索参数(nprobe、max_empty_result_buckets)与IVF_FLAT完全一致,适用于内存资源有限且可接受召回率轻微下降的场景。相较于IVF_FLAT,IVF_SQ8以精度的小幅损失换取了显著的内存优化。
IVF_PQ则进一步采用乘积量化(PQ)技术深化压缩,其流程为:先通过IVF进行聚类分区,再将每个高维向量分解为多个低维子向量,对各子向量独立量化后存储。该方法使索引文件体积较IVF_SQ8进一步减小,但精度损失更为明显。构建时除nlist参数外,还需配置m(乘积量化因子数,需满足向量维度dim能被m整除)和nbits(每个子向量的存储位数,范围[1,64],默认8)。搜索参数与IVF_FLAT、IVF_SQ8保持一致。
综合来看,IVF系列三种索引的内存占用呈现IVF_PQ < IVF_SQ8 < IVF_FLAT的关系,而召回率则相反,IVF_FLAT最优,IVF_SQ8次之,IVF_PQ最低。这种差异源于量化技术的逐步引入与深化,体现了存储效率与检索精度之间的权衡。
使用建议
- 选择IVF_FLAT:当内存充足且追求高精度时
- 选择IVF_SQ8:当内存有限但仍需要较高精度时
- 选择IVF_PQ:当处理超大规模数据且可接受精度损失时
基于图的索引:HNSW系列
图索引像是一张关系网,每个数据点都记录着自己的 “最近邻居”,搜索时能够沿着这张关系网快速跳转,实现高效查找。在前文中,我们使用过的HNSW索引就是一种典型的图索引。
HNSW索引是一种基于图的索引,在使用此索引构建向量后,能够提高搜索的精度(相对于使用量化的索引来说)和速度。但内存的消耗会增大。
分层导航小世界(HNSW)算法构建了一个多层图,有点像不同缩放级别的地图。底层包含所有数据点,而上层则由从底层采样的数据点子集组成。在这种层次结构中,每一层都包含代表数据点的节点,节点之间由表示其接近程度的边连接。上层提供远距离跳转,以快速接近目标,而下层则进行细粒度搜索,以获得最准确的结果。
下面是它的工作原理:
- 入口点:搜索从顶层的一个固定入口点开始,该入口点是图中的一个预定节点。
- 贪婪搜索:算法贪婪地移动到当前层的近邻,直到无法再接近查询向量为止。上层起到导航作用,作为粗过滤器,为下层的精细搜索找到潜在的入口点。
- 层层下降:一旦当前层达到局部最小值,算法就会利用预先建立的连接跳转到下层,并重复贪婪搜索。
- 最后 细化:这一过程一直持续到最底层,在最底层进行最后的细化步骤,找出最近的邻居。
# 创建HNSW索引
index_params = {
"index_type": "HNSW",
"metric_type": "L2",
"params": {
"M": 16, # 每个节点的最大连接数(朋友数量)
"efConstruction": 200 # 构建时的搜索范围
}
}
# 搜索时的参数
search_params = {
"ef": 64 # 搜索时的探索范围
}
HNSW的性能可以通过几个关键参数进行精细调节。M参数控制每个节点的连接数量,就像决定每个人维持多少社会关系。ef参数则控制搜索时的探索范围,相当于决定在寻找目标时要问多少个朋友。
这些参数的设置需要仔细权衡。过多的连接会增加网络复杂度和内存占用,但能提高搜索精度和速度。过大的探索范围能提高找到最优结果的概率,但也会增加搜索时间。在实际应用中,通常需要根据具体的硬件资源和工作负载特征来找到最佳配置。
M(连接度数):
- 作用:控制每个节点最多连接多少个邻居(朋友数量)
- 推荐值:16-48
- 影响:M越大,图越密集,精度越高,但构建时间和内存占用也越大
efConstruction(构建参数):
- 作用:构建索引时的搜索范围
- 推荐值:200-500
- 影响:值越大,索引质量越好,但构建时间越长
ef(搜索参数):
- 作用:查询时的搜索范围,控制探索的深度
- 推荐值:top_k到2000之间
- 影响:值越大,召回率越高,但查询时间越长
HNSW(Hierarchical Navigable Small World)通过构建多层导航图实现高效向量检索,其核心结构特点为上层稀疏、下层密集,搜索过程从上层起始逐步向下层精确导航,这一设计使其能够实现对数时间复杂度的查询性能,从而具备低延迟优势。HNSW在每层都是一个小世界图,通过图数据结构组织,上层节点也是通过投币决定在哪一层,并且它们在下层图中都有记录,
“投币”机制,是一种用于决定节点在图中所处层级的随机化策略。其原理类似于通过抛硬币(即概率决策)来确定新插入节点是否进入更高层图结构的过程。
- 新节点加入时,会先被放入最底层(第0层),然后以一定的概率(如50%)决定是否将其提升到上一层。这个过程可以形象地理解为“投币”:如果结果是“正面”,节点就进入上一层;如果是“反面”,则停留在当前层。
- 通常情况下,高层级的节点数量比低层级少得多,例如每向上一层,节点数量大约减半。这种设计保证了图结构的稀疏性,使得高层可用于快速导航,而底层则提供更精细的搜索能力。
- 上层图可以看做下层图的一个缩影,检索时,从上到下,不断指引检索过程逐步靠近想要探寻的向量空间。另外在构图过程中HNSW通过边的裁剪来保证图的连通性。
在构建阶段,通过参数M(图中节点最大传出连接数量,取值范围[2,2048])控制图的连接密度,efConstruction(构建时的搜索范围,取值范围[1,int_max])平衡索引质量与构建时间;搜索阶段则通过参数ef(搜索时的搜索范围,取值范围[top_k,int_max])调节查询时间与准确度的权衡。
为优化内存占用,HNSW系列衍生出多种量化变体,这些变体通常通过牺牲部分召回率和构建速度来换取内存效率的提升。HNSW_SQ结合标量量化(SQ)技术,将浮点数据离散化为有限数值(如SQ6量化为64个值),显著减少内存占用,同时保持较高的QPS性能,但构建时间较基础HNSW有所增加。HNSW_PQ则采用乘积量化(PQ),将向量分解为多个子向量并分别量化,可灵活权衡索引大小与准确性,但相比HNSW_SQ,其QPS和召回率更低,构建时间更长。
变体名称 | 核心技术 | 内存占用 | QPS性能 | 构建时间 | 召回率 | 主要特点 |
---|---|---|---|---|---|---|
HNSW | 分层导航小世界图 | 高 | 高 | 基准 | 高 | 适用于高速查询、高召回率场景,通过M和ef参数控制性能 |
HNSW_SQ | 标量量化(如SQ6) | 低 | 较高 | 适度增加 | 较高 | 将浮点数据离散化为有限数值(如64个值),显著减少内存占用 |
HNSW_PQ | 乘积量化 | 中-低 | 中 | 更长 | 中 | 向量分解为子向量量化,可灵活权衡索引大小与准确性 |
HNSW_PRQ | 乘积残差量化 | 中-低 | 中 | 增加数倍 | 与PQ相当 | 对PQ残差再次量化,压缩率控制更灵活,精度保持或提升 |
HNSW_PRQ作为基于乘积残差量化(PRQ)的变体,通过对PQ量化后的残差进行再次量化,进一步优化了索引大小与准确性的权衡灵活性。在相同压缩率下,HNSW_PRQ的QPS和召回率与HNSW_PQ相当,但其残差补偿机制使得在精度保持或提升的同时,能够实现更灵活的压缩控制,不过这也可能导致构建时间增加数倍。
HNSW vs IVF 对比
特性 | HNSW | IVF系列 |
---|---|---|
查询速度 | 通常更快(对数时间复杂度) | 较慢 |
内存占用 | 很高(需要存储图结构) | 中等到低 |
精度 | 高 | 取决于nprobe设置 |
构建时间 | 中等 | 较短 |
适用场景 | 低延迟、高精度需求 | 内存敏感、大规模数据 |
使用建议
选择HNSW:
- 对查询延迟要求严格
- 内存资源充足
- 数据量不是特别大(千万级以下)
- 需要高召回率
不选择HNSW:
- 内存资源有限
- 数据量特别大(亿级)
- 对构建时间有严格要求
其他浮点索引:SCANN与DiskANN
在浮点嵌入索引中,SCANN与DiskANN作为两类重要的优化方案,分别在查询性能与大规模数据存储方面展现了独特优势。
SCANN(可扩展近邻)与IVF_PQ在基础架构上具有相似性,但通过引入SIMD(单指令多数据)优化显著提升了计算效率,尤其针对GPU环境进行了深度适配,从而实现了更高的查询吞吐量(QPS)。其核心优势在于极高速查询能力与高召回率,适用于内存资源充足的场景。在参数配置方面,SCANN的构建参数包括nlist(聚类数量)和with_raw_data(是否包含原始数据,默认值为True);搜索阶段则可通过调整nprobe(查询的聚类中心数量)、reorder_k(候选向量数量,默认值为top_k)及max_empty_result_buckets(范围搜索参数,默认值为2)进一步优化性能。
索引类型 | 核心技术 | 适用场景 | 构建参数 | 搜索参数 | |
---|---|---|---|---|---|
SCANN | 类IVF_PQ架构,SIMD优化,GPU适配 | 极高速查询、高召回率、内存资源充足场景 | nlist(聚类数量)、with_raw_data(默认True) | nprobe、reorder_k(默认top_k)、max_empty_result_buckets(默认2) | |
DiskANN | Vamana图结构,PQ量化,磁盘存储 | 内存无法容纳的十亿级及以上大规模数据集 | - | - |
DiskANN则专注于解决内存资源限制下的大规模数据存储问题,其技术路径基于Vamana图结构,并结合乘积量化(PQ)技术实现磁盘-内存混合存储。具体而言,DiskANN将图结构存储于磁盘以突破内存容量限制,同时通过PQ对向量进行压缩,减小数据体积并加速近似距离计算,有效缓解了磁盘IOPS瓶颈。这一设计使其特别适用于内存无法容纳的十亿级及以上规模数据集,在平衡查询延迟与存储容量方面具有不可替代性 。
二进制嵌入索引
二进制嵌入索引通过将向量转换为二进制表示形式,显著提升了存储效率。其存储空间计算公式为n/8字节(n为向量维度),例如128维二进制向量仅需16字节,远低于同等维度浮点向量的存储需求。该类索引支持JACCARD和HAMMING两种距离度量方式,主要包含两种典型实现:BIN_FLAT和BIN_IVF_FLAT。
索引类型 | 适用场景 | 支持的距离度量 | 存储效率 | 核心特点 |
---|---|---|---|---|
BIN_FLAT | 小数据集、需100%召回率、无需压缩的场景 | JACCARD、HAMMING | n/8字节(n为向量维度) | 精确检索,与FLAT原理一致 |
BIN_IVF_FLAT | 高速查询、高召回率的场景 | JACCARD、HAMMING | n/8字节(n为向量维度) | 基于IVF结构,通过量化加速查询 |
其中,BIN_FLAT索引与FLAT索引原理一致,专为二进制嵌入设计,适用于小数据集、需100%召回率且无需压缩的场景;BIN_IVF_FLAT则基于倒排文件(IVF)结构,通过量化加速查询过程,适合对查询速度和召回率均有较高要求的场景。在选型边界方面,二进制嵌入索引与浮点索引的核心区别由向量类型决定:当处理对象为二进制向量时,应优先选择二进制嵌入索引以充分发挥其存储效率优势;若向量为浮点类型,则需采用浮点索引类型。
稀疏嵌入索引
稀疏嵌入的核心特征是向量中非零值占比极低,这种稀疏性使其在存储和计算上具有独特优势,但也对索引结构提出了特殊要求。稀疏嵌入索引主要支持内积(IP)和BM25(全文检索)度量方式,目前主流的索引类型包括SPARSE_INVERTED_INDEX和SPARSE_WAND两种。
索引类型 | 适用场景 | 召回率 | 性能特点 | 关键参数 |
---|---|---|---|---|
SPARSE_INVERTED_INDEX | 小数据集 | 100% | 确保完全召回,大规模数据效率有限 | 无 |
SPARSE_WAND | 需平衡速度与召回率场景 | 接近100% | 弱-AND算法加速,向量密度增加时性能下降,通过参数调整精度与性能平衡 | drop_ratio_search(范围[0,1]) |
SPARSE_INVERTED_INDEX(稀疏反转索引)适用于小数据集场景,其设计目标是确保100%的召回率,即在检索过程中不会遗漏任何相关结果,但在处理大规模数据时效率相对有限。
SPARSE_WAND(稀疏弱AND索引)则通过引入弱-AND(Weak-AND)算法实现性能优化。该算法的核心机制是减少对完整内积(IP)距离的计算次数:通过预设阈值筛选出对距离计算贡献较大的非零维度,仅对这些关键维度进行计算,从而显著提升检索速度。然而,SPARSE_WAND的性能表现与向量密度密切相关——当向量密度(非零值比例)增加时,需要计算的维度增多,弱-AND算法的筛选效率下降,导致性能出现衰减。为平衡检索精度与性能,SPARSE_WAND提供了drop_ratio_search参数(取值范围为[0,1]),该参数通过设置忽略小值的比例,进一步减少参与计算的非零维度数量,从而在牺牲少量召回率的前提下提升检索效率。
GPU索引
本文对于GPU索引的介绍可能并不完善,仅供参考。PS:(作者水平不高/(ㄒoㄒ)/~~)
GPU索引通过利用GPU的并行计算能力,显著提升向量索引构建与搜索过程的吞吐量,尤其适用于极高吞吐量场景,但其加速效果主要体现在数据处理规模上,不一定能直接减少单次查询的延迟。不同类型的GPU索引在内存占用、精度、查询性能等方面各具特性,适用于不同业务需求场景。
索引类型 | 内存占用情况 | 精度/召回率 | 主要特点 | 适用场景 |
---|---|---|---|---|
GPU_CAGRA | 约为原始数据的1.8倍 | - | 基于图结构优化,提供丰富构建(如intermediate_graph_degree)与搜索参数(如itopk_size),低延迟表现突出 | 极高吞吐量场景,对延迟敏感的业务需求 |
GPU_IVF_FLAT | 与原始数据等大 | - | 查询时间随nq(输入向量数)和nprobe(搜索簇数)增加急剧上升 | 中小规模查询场景 |
GPU_IVF_PQ | 取决于压缩参数,小于IVF_SQ8 | 精度损失较大 | 通过向量压缩降低内存占用,索引文件较小 | 内存资源有限且可接受一定精度损失的场景 |
GPU_BRUTE_FORCE | 与原始数据等大 | 100% 召回率 | 暴力搜索,仅需配置metric_type和top_k参数 | 对结果准确性要求极高、数据规模较小的场景 |
GPU_CAGRA是基于图结构的GPU优化索引,其内存使用量约为原始数据的1.8倍。该索引提供了丰富的构建与搜索参数以平衡性能,例如构建阶段可通过调整intermediate_graph_degree(剪枝前图度数,推荐32或64)、graph_degree(剪枝后图度数,需小于前者)、build_algo(支持IVF_PQ或NN_DESCENT算法)等参数优化图结构;搜索阶段则可通过itopk_size(中间结果大小,至少为目标top_k且通常设为2的幂次)、search_width(入口点数量)、迭代次数范围(min_iterations/max_iterations)等参数控制搜索效率与精度。由于其图结构优化和并行计算适配性,GPU_CAGRA在低延迟场景中表现突出。
GPU_IVF_FLAT的原理与CPU版本的IVF_FLAT类似,需占用与原始数据等大的内存空间。其查询时间对输入向量数量(nq)和搜索簇数(nprobe)较为敏感,两者的增加会导致查询时间急剧上升,因此更适合中小规模查询场景。
GPU_IVF_PQ通过向量压缩降低内存占用,其索引文件大小小于IVF_SQ8,但会引入一定的精度损失,内存占用量取决于具体的压缩参数配置,适用于内存资源有限且可接受一定精度损失的场景。
GPU_BRUTE_FORCE采用暴力搜索方式,能够实现100%的召回率,但其内存需求与原始数据相当,且仅需配置metric_type(距离度量类型)和top_k(返回结果数量)两个核心参数,适用于对结果准确性要求极高、数据规模较小的场景。
在高并发场景中,GPU索引凭借并行计算优势可高效处理大量并发查询,显著提升系统整体吞吐量。然而,其应用需权衡硬件成本:GPU设备本身购置成本较高,且不同索引对内存容量要求各异(如GPU_CAGRA需1.8倍原始数据内存,GPU_IVF_FLAT与GPU_BRUTE_FORCE需等大内存),因此需结合业务对延迟、召回率、吞吐量的需求及硬件预算综合选择。
代码实践
学习完上述的索引的知识后,我们将通过代码来更加可观的观察不同索引在定量数据集上的表现。
点击:代码实践 运行如下命令,安装需要的包
pip install pymilvus prettytable matplotlib
运行如下命令,运行代码
python milvus_perf_test.py
关键性能指标与权衡关系
核心性能指标定义
ANNS向量索引的核心性能指标是评估和选择索引方案的基础,主要包括构建时间、每秒查询次数(QPS)、召回率及内存使用量。其中,构建时间指索引创建过程所消耗的时间,直接影响索引的初始化效率;每秒查询次数(QPS)反映搜索操作的效率,即单位时间内可处理的查询请求数量;召回率衡量搜索结果的准确性,体现返回的相关结果占所有相关结果的比例;内存使用量则表征索引对系统内存资源的占用情况,其大小受数据结构设计、量化压缩策略及精炼器等因素影响 。
在实际应用中,需根据场景需求明确各指标的优先级逻辑。例如,实时应用(如在线推荐、实时检索服务)对响应速度要求严苛,QPS通常作为核心优先级指标;离线分析场景(如大规模数据挖掘、批量检索任务)更注重结果的完整性和准确性,此时召回率应优先考虑;而构建时间和内存使用量需结合资源约束进行权衡,如高频更新的动态数据集对构建时间敏感,资源受限的边缘设备则需重点关注内存占用。通过建立包含上述指标的评估框架,可实现对索引性能的量化分析,为后续索引选择决策提供客观依据。
性能权衡规律
ANNS向量索引的选择需遵循“速度-精度-内存”三角权衡模型,即提升某一维度性能往往以牺牲其他维度为代价。以下结合具体索引案例分析其内在规律:
在速度(查询效率) 维度,基于图的索引(如HNSW)通常表现更优,其QPS(每秒查询次数)普遍高于IVF系列变体。这一优势在小topK(如小于2000)场景中尤为显著,而当topK较大(如大于2000或占数据集比例≥1%)时,IVF变体因分桶机制更适配批量查询需求。此外,过滤率(搜索前过滤数据比例)也影响速度表现:过滤率<85%时图索引效率占优,85%-95%时IVF更合适,>98%时则需采用暴力搜索(FLAT)以平衡精度与计算成本。
精度(召回率) 方面,量化技术(如PQ、SQ)是内存优化的主要手段,但会导致精度损失。相同压缩率下,PQ较SQ能保留更高召回率,但查询速度略慢;而IVF系列(如IVF-PQ)虽通过量化显著降低内存占用,其召回率通常低于非量化索引(如HNSW)。对于极致精度需求(如过滤率>98%),FLAT索引仍是唯一选择,因其无近似计算误差。
内存占用 是资源受限场景的核心考量。IVF系列(如IVF-PQ)通过乘积量化技术实现高效压缩,内存消耗仅为11MB/百万128维向量,远低于HNSW的640MB/百万128维向量。对于超大规模数据集,DiskANN可通过磁盘存储管理数据,但需注意潜在的IOPS瓶颈对查询速度的影响。
索引类型 | QPS表现 | 召回率适用过滤率 | 内存占用(128维向量/百万) | 适用场景 |
---|---|---|---|---|
HNSW(图索引) | 通常优于IVF变体 | <85% | 640MB | 小topK(<2000)、高召回率、高吞吐低延迟场景 |
IVF变体 | 低于HNSW | 85%-95% | 取决于量化技术 | 大topK(>2000或≥数据集1%)、批量查询 |
IVF-PQ | - | - | 11MB | 内存受限场景,需结合mmap |
FLAT | 最低(暴力搜索) | >98% | 最高(无压缩) | 极致精度需求,过滤率>98% |
DiskANN | 受IOPS瓶颈影响 | - | 磁盘存储,内存占用低 | 超大规模数据集,磁盘级数据管理 |
索引选择决策
决策关键因素
在选择ANNS向量索引时,需综合考虑多维度决策关键因素,这些因素可系统地分为数据特性、资源约束和业务需求三大类,并需建立清晰的优先级排序逻辑以指导选择过程。
我们将使用通俗的话来解释这三大类:
- 数据特性:数据特性主要设计数据的存储位置,如内存、磁盘等。这直接影响索引的存储设计与访问效率。
- 资源约束:内存资源是核心考量因素,包括内存是否有限或充足,以及是否需要通过量化技术或磁盘存储来优化内存使用。
- 业务需求则涵盖多个关键性能指标,包括
- 召回率要求(如高召回率或可接受一定程度的召回率下降)
- 查询延迟(如低延迟或可接受范围内的延迟)
- topK大小(如小批量或大批量结果返回)
- 过滤率(如低过滤率或高过滤率需求)
- 整体性能要求(如QPS、延迟与召回率的平衡)
例如:在政务问答系统中,往往我们需要召回大量的数据,尽可能的将模型上下文拉满,以获得更好的结果。因此,我们需要选择一个高召回率的索引,同时考虑到内存资源有限,我们可能需要选择一个内存占用较低的索引,如IVF-PQ结合mmap。除此之外,我们还需考虑到查询延迟,因为政务问答系统需要实时返回结果,因此我们需要选择一个查询延迟较低的索引,如HNSW。最后,我们需要考虑整体性能要求,选择一个综合考虑上述关键因素的索引,如HNSW、IVF+精炼。 为帮助用户系统选择适合的ANNS向量索引,可基于实际应用场景构建决策矩阵,并将其转化为分步骤筛选流程。以下为整合后的推荐索引决策矩阵:
场景 | 推荐索引 | 注释 |
---|---|---|
原始数据适合内存 | HNSW、IVF+精炼 | HNSW适合低k/高召回率 |
原始数据在磁盘/固态硬盘 | DiskANN | 适合延迟敏感查询 |
原始数据在磁盘且RAM有限 | IVFPQ/SQ+mmap | 平衡内存与磁盘访问 |
高过滤率(>95%) | FLAT(暴力) | 避免候选集过小的索引开销 |
大型k(≥数据集的1%) | IVF | 簇剪枝减少计算量 |
极高召回率(>99%) | FLAT+GPU | -- |
基于上述矩阵,索引选择流程可分为以下步骤:首先,判断数据存储位置及内存适配情况。若数据可完全加载至内存,优先考虑HNSW(适用于低k值、高召回率场景)或IVF+精炼组合;若数据存储于磁盘或固态硬盘且对查询延迟敏感,选择DiskANN;若数据在磁盘且内存资源有限,则采用IVFPQ/SQ结合内存映射(mmap)以平衡内存占用与磁盘访问效率 。其次,考虑查询特性与需求:当过滤率超过95%时,FLAT(暴力搜索)可避免索引带来的额外开销;当查询k值较大(≥数据集规模的1%),IVF通过簇剪枝能有效减少计算量;若需极高召回率(>99%),建议使用FLAT+GPU方案。
此外,需注意例外情况:在GPU环境下,应优先考虑GPU加速的索引(如GPU_CAGRA),以充分利用硬件算力提升查询性能。通过上述流程,可根据具体场景快速定位最优索引方案。
核心索引参数
核心索引参数的调优是平衡向量检索系统召回率、查询性能(如QPS)及资源消耗的关键环节。不同类型的ANNS索引具有独特的核心参数,其调整机制直接影响索引的构建效率、查询速度及检索精度。
索引类型 | 参数名称 | 参数作用 | 推荐值范围 | 性能影响 |
---|---|---|---|---|
IVF系列 | nlist | 决定向量空间聚类划分粒度 | 百万级数据1024 | 增大可提升搜索速度,但可能降低召回率 |
IVF系列 | nprobe | 控制查询时遍历的簇数量 | 8-100 | 增大可提高召回率,但会增加查询时间 |
HNSW | M | 图中每个节点的连接度数 | 16-48 | 增大可提升检索精度,但增加构建时间和内存占用 |
HNSW | efConstruction | 索引构建阶段的搜索范围参数 | 200-500 | 增大可提升索引质量,但延长构建时间 |
HNSW | ef | 查询阶段的搜索范围参数 | top_k-2000 | 增大可提高搜索精度,但降低查询速度 |
SCANN | reorder_k | 重排序候选向量数量 | top_k-1000 | 增大可提高召回率,但增加计算量和查询时间 |
稀疏索引 | drop_ratio_search | 查询时忽略小值元素的比例 | - | 调整可在精度与性能之间取得平衡 |
内存与存储优化策略
在向量索引的实际部署中,内存与存储资源的高效利用是关键挑战之一。以100万128维向量为例,不同索引类型的内存占用差异显著,如IVF_PQ索引仅需10MB左右的内存,而HNSW_PQ索引则需136MB,这种差异凸显了优化策略的重要性。
下面以常见参数配置为例,计算 PQ 压缩后的向量内存占用: 关键参数:
- 向量数量 (n):1,000,000
- 向量维度 (d):128
- PQ 子空间数量 (M):通常设置为 8, 16 或 32。对于 128 维向量,M = 8 是一个极其常见的选择(因为 128 / 8 = 16,能整除)。
- 每个子量化的比特数 (nbits):通常为 8(意味着每个子量化簇中心数为 256,即 2^8)。
nbits = 8 意味着每个子向量的 PQ 编码是一个 uint8(1 字节)的数字。
计算过程:
- 每个向量的 PQ 编码大小 = M(子空间数) × ceil(nbits / 8)(每个子编码的字节数) 代入参数:8 × ceil(8 / 8) = 8 × 1 = 8 字节
- 所有向量的 PQ 编码总大小 = 1,000,000 × 8 字节 = 8,000,000 字节
- 换算为 MB:8,000,000 字节 / (1024 * 1024) ≈ 7.63 MB
所以,经过 PQ 压缩后,100 万个向量本身只需要大约 7.6 MB 的内存。(具体怎么算的,可以去看chapter4的FusionANNS章节)
HNSW 的核心是一个多层图结构,这部分才是内存占用的大头。下面以常见参数为例,计算 HNSW 图结构所需的内存占用:
HNSW 图结构需要存储以下信息:
- 节点的连接信息(邻接表):每个节点(即每个向量)都有一系列指向其“邻居”的链接,这是内存消耗的主要部分。
- 图层级信息:每个节点属于哪些层。
计算 HNSW 图占用空间的公式可以简化为: 总内存 ≈ 向量数 × 平均连接数 × 每个链接的成本
关键参数:
- efConstruction:控制索引构建时邻居列表的动态大小,影响最终的平均连接数。
- M (在 HNSW 中这个参数通常叫 M,但和 PQ 的 M 不同):每个节点在每一层的最大连接数。这是一个核心参数,通常设置为 16 或 32。我们这里假设 M = 16。
- 平均连接数 (avg_links):由于 HNSW 是多层结构,底层连接数多,顶层连接数少。实际的平均连接数会大于 M。经验上,平均连接数大约是 M * 2 左右。我们取 M * 2 = 32。
- 每个链接的成本:每个链接本质上存储的是一个节点的 id(标识符)。在百万量级下,一个 id 通常用 4 字节的 uint32 或 8 字节的 uint64 来存储。我们这里按 4 字节 计算(更节省的情况)。
计算过程:
- 每个节点的连接信息大小 ≈ 平均连接数 × 每个链接的字节数 = 32 × 4 字节 = 128 字节
- 假设向量数为 1,000,000,所有节点的连接信息总大小 = 1,000,000 × 128 字节 = 128,000,000 字节
- 换算为 MB:128,000,000 字节 / (1024 * 1024) ≈ 122.07 MB
此外,图结构还有一些其他开销(如图层级索引、算法本身的数据结构开销),但主要部分就是邻接表。
上述计算是基于典型参数的理论估算。实际占用会根据您使用的库(FAISS, HNSWlib 等)、具体参数(PQ的M、HNSW的M、nbits)和实现优化程度略有浮动
内存规划可遵循以下步骤:首先,根据数据集规模(如向量数量、维度)和选定的索引类型(如IVF、HNSW等)估算基础内存需求。若估算结果超出可用内存,则需采用针对性优化手段。常用策略包括量化、内存映射(mmap)及磁盘存储方案(如DiskANN)。
优化策略 | 技术方法 | 效果描述 |
---|---|---|
量化 | PQ(乘积量化) | 实现64倍压缩 |
量化 | SQ(标量量化) | IVF_SQ8索引减少75%内存消耗 |
内存映射(mmap) | 直接访问磁盘文件 | 减少I/O开销,扩展存储容量 |
DiskANN | 图结构存储于磁盘 | 处理内存无法容纳的超大型数据集 |
量化技术通过压缩向量表示降低内存占用,典型方法包括乘积量化(PQ)和标量量化(SQ)。例如,PQ可实现64倍压缩,而IVF_SQ8索引通过标量量化能减少75%的内存消耗。内存映射(mmap)技术允许直接访问磁盘文件,在减少I/O开销的同时扩展存储容量,适用于内存资源有限但磁盘空间充足的场景。对于内存无法容纳的超大型数据集,可采用DiskANN等磁盘存储方案,将图结构存储于磁盘以处理大规模数据 。通过结合上述策略,可在保证检索性能的前提下,有效降低内存与存储成本。
特殊场景与最佳实践
超大规模数据集(十亿级)
在处理十亿级规模的超大规模向量数据集时,由于内存资源通常无法完全容纳全部数据,需依赖磁盘存储与内存访问的协同优化。DiskANN是应对此类场景的关键技术之一,其核心原理是通过磁盘存储Vamana图结构,并结合乘积量化(PQ)压缩技术,实现对内存无法容纳的超大规模数据集的高效检索 。这种“磁盘图结构+压缩存储”的设计,既能突破内存容量限制,又能通过PQ量化减少数据传输与存储开销。
在延迟表现方面,与基于内存映射(mmap)的方案(如IVF_PQ/SQ+mmap)相比,DiskANN的延迟更为稳定。mmap方案虽通过内存映射平衡内存占用与磁盘访问,但磁盘I/O的随机性可能导致延迟波动;而DiskANN通过优化的图遍历策略与预取机制,可减少磁盘访问的不确定性,从而提供更一致的响应延迟。
针对十亿级数据集的索引组合方案,实践中主要有两类选择:一是采用DiskANN结合PQ量化,利用磁盘存储图结构并通过PQ压缩向量,适用于极致内存受限场景 ;二是使用IVF_PQ/SQ与mmap结合,通过IVF的粗聚类减少检索范围,配合PQ/SQ量化降低内存占用,同时借助mmap实现磁盘数据的高效内存访问,在内存与延迟之间取得平衡。具体选择需根据实际内存资源、延迟需求及数据特性综合考量。
高并发低延迟场景
在高并发低延迟场景中,向量索引的选择需重点平衡查询吞吐量、响应时间与资源成本。基于图的索引(如HNSW)凭借对数时间复杂度的搜索特性,以及DiskANN针对延迟敏感查询的优化设计,成为该场景下的核心选择 。此外,GPU加速索引(如GPU_CAGRA)通过硬件并行计算能力,可显著提升高并发处理效率。
索引类型 | 技术特点 | 适用场景 | 优化参数 | 参考来源 |
---|---|---|---|---|
HNSW | 基于图的对数时间搜索 | 低延迟需求 | ef(探索节点数) | |
DiskANN | 针对延迟敏感查询优化 | 低延迟需求 | - | |
GPU_CAGRA | GPU加速并行计算 | 高并发、批量查询(nq>1000) | search_width(搜索宽度) | - |
从硬件性价比角度看,CPU与GPU的适用场景存在差异:GPU更适合处理批量查询规模较大(nq>1000)的高并发场景,其并行计算架构能有效降低单位查询的处理延迟;而CPU在小规模查询或资源受限环境中可能更具成本优势。
硬件类型 | 适用场景 | 优势 | 限制 |
---|---|---|---|
CPU | 小规模查询、资源受限环境 | 成本优势 | 并行处理能力有限,高并发下延迟较高 |
GPU | 批量查询规模较大(nq>1000) | 并行计算架构,降低单位查询处理延迟 | 资源成本较高,小规模查询性价比低 |
针对低延迟优化,实践中可采用“索引类型+参数调优+硬件加速”的组合策略。例如,HNSW索引可通过增大ef(查询时的探索节点数)参数提升搜索效率,GPU_CAGRA则可调整search_width(搜索宽度)参数平衡延迟与召回率。结合GPU加速技术,此类组合能在保证高召回率的同时,满足毫秒级甚至微秒级的低延迟需求。
常见问题解答
FLAT与IVF_FLAT的核心差异在于检索方式:FLAT索引通过直接比较查询向量与数据集中所有向量获得精确结果,而IVF_FLAT索引先将向量聚类为nlist个簇,查询时仅比较距离最近的nprobe个簇内向量以减少计算量。当数据集向量总数接近nlist时,IVF_FLAT的聚类优势不明显,性能与FLAT相近;当向量数量远大于nlist时,IVF_FLAT通过减少比较次数显著提升效率 。此处nlist(聚类簇数量)和nprobe(查询时检查的簇数)需协同调整:nlist过小会导致簇内向量过多,削弱加速效果;nprobe过大会增加计算量,接近FLAT的性能。
索引类型 | 检索方式 | 比较向量范围 | 性能关键参数 | 性能特点 | 适用场景 |
---|---|---|---|---|---|
FLAT | 直接比较所有向量 | 全部向量 | 无 | 精确结果,计算量大,性能随数据量线性下降 | 小数据集,对精度要求极高的场景 |
IVF_FLAT | 先聚类为nlist个簇,查询时比较nprobe个簇内向量 | 距离最近的nprobe个簇内向量 | nlist(簇数量)、nprobe(查询簇数) | 近似结果,计算量减少,数据量远大于nlist时优势显著 | 大数据集,需平衡精度与速度的场景 |
PQ与SQ的压缩率差异体现在量化粒度:标量量化(SQ)如IVF_SQ8对每个向量维度独立压缩,将4字节浮点值转为1字节整数,内存占用减少75%;乘积量化(PQ)如IVF_PQ(以8个子量化器为例)将向量分割为子向量后分别量化,可将512字节向量压缩至8字节,实现64倍压缩率。两者相比,PQ压缩率远高于SQ,但可能伴随更高的精度损失。
压缩倍数对比数据:
- IVF_SQ8 索引:4倍压缩
- IVF_PQ 索引:64倍压缩
量化策略 | 压缩方式 | 原始大小 (每向量) | 压缩后大小 (每向量) | 内存减少比例 | 压缩倍数 |
---|---|---|---|---|---|
IVF_SQ8 | 标量量化,每个维度独立压缩 | 4字节/维度 | 1字节/维度 | 75% | 4倍 |
IVF_PQ | 乘积量化,8个子量化器分割向量 | 512字节 | 8字节 | 约98.44% | 64倍 |
避坑指南方面,小数据集应避免过度量化:由于数据量有限,FLAT或IVF_FLAT索引已能满足性能需求,使用IVF_PQ等强压缩索引反而可能因信息损失导致精度下降。此外,配置IVF类索引时需根据数据规模合理设置nlist(建议为数据量的平方根量级),并通过nprobe平衡召回率与速度(默认值通常为10,需根据精度需求调整)。
最后
以上是一个常见的索引优化建议,供学习者学习和参考,具体实现需结合实际数据集和需求进行微调。 对于企业级的检索系统来说,并不能单纯的选择一种或几种索引来优化检索,企业的数据集往往是亿级别的,并且召回率和召回速度要求非常高,普通的索引并不能很好的解决问题。为了提高召回率,部分企业会选择暴力索引来强行拉高召回率,但是采用这个索引的前提是,在召回之前,添加选择器,
选择器:(如聚类中心、图路由、学习型路由器、标量过滤器等)本身就是一个近似检索过程。它需要在极短时间(毫秒级)内决定访问哪些分区,这决定了它不可能做到 100% 准确。对于某些“边界”或“模糊”的 query,其真正相关的向量可能均匀散布在大量分区中,或者集中在某个未被选择器选中的“冷门”分区里。选择器很难完美处理所有边界情况。
避免暴力检索所有的数据,尽可能的在选择器的帮助下将搜索范围缩小到n个分区中,再进行暴力检索。不过这种方法存在一些问题,对于一个query,选择器选择出需要检索的k个分区,但是这k个分区中可能存在重复的向量,那么暴力检索就会重复计算。除此之外,可能在其他分区中,也存在会对最终结果造成很大影响的数据,你不能确定和保证其他分区里面没有你需要的数据。这是该策略最致命的弱点。一旦包含关键结果的分区被选择器漏掉,无论该分区内的向量与 query 多么相关,都没有机会进入最终结果集。这与暴力索引追求高召回的目标背道而驰。 对于大量不太热门或特征不典型的 query(长尾 query),选择器基于常见模式训练或设计,更容易出错,遗漏风险更高。
追求全局最优不可能: 在亿级甚至更大规模下,同时实现 100% 召回率、极低延迟和低成本是不可能的“三元悖论”。“暴力索引+选择器”是一种试图在可控成本/延迟下逼近高召回率的工程妥协。 你可以选择
- 改进选择器
- 优化分区策略
- 缓解重复计算
- 分层检索
在分区内不使用纯暴力检索,而是使用针对该分区优化过的精确或近似索引(如 HNSW, IVF-PQ)。这能显著加速分区内检索,即使 k 值稍大或存在少量重复,整体延迟也可能可接受。这本质上是用分区内的索引加速来换取选择器可以放宽一些精度要求(允许选稍多分区或降低选择器复杂度),从而间接缓解遗漏问题。
(怎么解决?参考FusionAnns优化方案)